home *** CD-ROM | disk | FTP | other *** search
- /*
- 1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
- | | | | | | | | | |
-
- */
-
- /*
- ** DoubleBuffers
- **
- ** This module contains the routines that deal with Double Buffering.
- **
- ** (NMD) 9//91 created
- ** (NMD) 9/22/91 DoubleBuffer - the high level interface to double buffering a file
- ** (NMD) 9/22/91 AllocateBuffers - allocates a pair of same-sized buffers. Handy
- ** for headers and the actual double buffers. Only leaves memory
- ** if it completes.
- ** (NMD) 9/22/91 SetUpDBPrivateMem - sets up memory and pointers for our private
- ** data. As with AllocateBuffers, only leaves memory allocated if
- ** it completes succesfully.
- ** (NMD) 9/22/91 FreeDBPrivateMem - Gets rid of any remaining memory allocated by
- ** SetUpDBPrivateMem. checks to make sure it isn't stomping all
- ** over things (hopefully).
- ** (NMD) 9/22/91 PrimeBuffers - pre loads the buffers, so we have some data so start
- ** the buffering process with.
- ** (NMD) 10/4/91 QueueFrame - queues up a bufferCmd followed by a CallBackCmd. The
- ** Callback queues up yet another bufferCmd and callback (if we're
- ** not out of data) so as to keep things chugging along.
- ** (NMD) 10/4/91 DBService - this is the callback routine that takes care of
- ** queueing up another buffer/callback pair, switching and refilling
- ** the buffers.
- ** (NMD) 10/4/91 CompleteRead - the completion routine for the asynch read. Set flags
- ** and convert the buffer from 2's compliment notation to binary offset.
- ** (NMD) 10/6/91 ***Changed from straight double buffering to multiple buffering model***
- ** (NMD) 10/10/91 Included a stripped down read parameter block at the beginning of
- ** each header to avoid problems where the parameter block was busy
- ** when we tried to queue a read.
- ** (NMD) 1/17/92 Modified SetUpDBPrivateMem to be a little prettier - only one
- ** return point now.
- ** (NMD) 1/17/92 Modified DoubleBuffer to have a more aesthetically pleasing
- ** countenance...
- */
-
-
- #pragma load "MacHeaders"
-
-
-
- #ifndef __MAININCLUDES__
- #include "MainApp.h"
- #endif
-
- #ifndef __DOUBLEBUFFERINCLUDES__
- #include "DoubleBuffer.h"
- #endif
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // application globals
-
- // The sound channel used for playing a sound, duh.
- SndChannelPtr gSndChan;
-
- // "Stop processing this" flag
- static char gsStopFlag;
-
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // do all of the initialization necesary
-
- OSErr InitDoubleBuffer(void)
- {
- OSErr err;
-
- gSndChan = nil;
- err = SndNewChannel(&gSndChan, sampledSynth, 0, (SndCallBackProcPtr)DBService);
- return (err);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- void CloseDoubleBuffer(void)
- {
- OSErr err;
-
- err = SndDisposeChannel(gSndChan, true);
- }
-
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // Allocates memory required for a PrivateDBInfoPtr
- PrivateDBInfoPtr SetUpDBPrivateMem(void)
- {
- PrivateDBInfoPtr dbInfo = nil; // top level of structure
- short index = 0; // index for buffer elements
- Boolean failure = false; // allocation failure flag
- long requiredMem;
- long totalSize;
- long contigSize;
-
-
- // Allocate space for our private information. Allocate top level of the structure, then allocate space for the
- // header information and the associated buffers. Just to make our life a little easier, we do a little bit of
- // pre-flighting to see if there is anywhere near enough memory.
-
- PurgeSpace (&totalSize, &contigSize);
-
- // Compute required space for our buffers, variables and soundheaders
- requiredMem = (sizeof (PrivateDBInfo) + kNumBuffers*(kBufferSize + sizeof (SoundHeader)));
- Assert ((requiredMem > totalSize), "\pMemory preflight failed...");
-
- // If there's enough memory, continue with allocating the space for dbPrivateInfo
- if (requiredMem < totalSize) {
- DebugMessage ("\pabout to allocate memory for dbInfo");
-
- // Allocate memory for the root level structure
- dbInfo = (PrivateDBInfoPtr) NewPtrClear (sizeof (PrivateDBInfo));
- if (dbInfo != nil){
-
- dbInfo->signature = 'dbBf'; // mark this as ours
-
- do { // for each buffer...
- DebugMessage ("\psetting up memory for a SoundHeader");
-
- // Allocate memory for the sound header
- dbInfo->buffers[index].header = (SoundHeaderPtr) NewPtrClear (sizeof(SoundHeader));
-
- // If we succesfully allocated memory for the header, allocate the associated buffer
- if (dbInfo->buffers[index].header) {
- DebugMessage ("\psetting up memory for associated buffer");
- dbInfo->buffers[index].header->samplePtr = NewPtrClear (kBufferSize);
-
- // Check to see if the allocation succeded - if not, fail.
- if (dbInfo->buffers[index].header->samplePtr == nil) {
- failure = true;
- break;
- }
- } else { // if header alloc failed
- failure = true; // mark and leave
- break;
- }
- } while (++index < kNumBuffers);
- } else
- failure = true; // main allocation failed
- }
-
- Assert (failure, "\pran out of memory");
- if (failure) {
- FreeDBPrivateMem (dbInfo); // free any allocated memory
- return (nil); // return nothing
- } else
- return (dbInfo); // return built structure
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // deallocates all the memory used in a PrivateDBInfoPtr
- void FreeDBPrivateMem (void *freeSpace)
- {
- short index = 0; // index for buffer elements
- PrivateDBInfoPtr dbInfo = 0;
-
- dbInfo = (PrivateDBInfoPtr) freeSpace; // easier access to elements
-
- if (dbInfo->signature == 'dbBf') { // make sure this is ours
-
- // Since we allocated dbInfo with NewPtrClear, we just need to check for a non-zero
- // value to know it's safe to free. Let's not corrupt those master pointers...
- if (dbInfo) {
- DebugMessage ("\pabout to dispose of scads of memory");
- do { // for each buffer rec
- if (dbInfo->buffers[index].header->samplePtr) // get rid of buffer
- DisposPtr (dbInfo->buffers[index].header->samplePtr);
- if (dbInfo->buffers[index].header)
- DisposPtr ((Ptr) dbInfo->buffers[index].header); // get rid of header
- } while (++index < kNumBuffers);
- DisposPtr ((Ptr) dbInfo); // dispose of top level
- }
- }
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // does all the setup for double buffering a file, and starts the process going
- OSErr DoubleBuffer (SndChannelPtr chan, unsigned long fileRefNum, ProcPtr readproc, ProcPtr processproc,
- SoundHeaderPtr generalHeader, unsigned long playSize, long dataOffset, Ptr *privateData)
- {
- OSErr err = noErr; // error bucket
- PrivateDBInfoPtr dbInfo = nil;
-
- // Clear the stop flag
- gsStopFlag = false;
-
- // We're going to use a PrivateDBInfo structure to hold all the information we'll need
- // later on to do our double buffering. The next several lines of code deal with allocating
- // space for it and it's members and initializing fields.
- dbInfo = SetUpDBPrivateMem ();
- if (dbInfo != nil) {
- DebugMessage ("\p allocated dbInfo successfully");
-
- *privateData = (Ptr)dbInfo;
-
- // Install the read procedure. This is mandatory. life is over if it's not present...
- if (readproc == nil) {
- Assert (readproc == nil,"\pNo readproc specified");
- } else {
- DebugMessage ("\p Have a valid readproc");
- dbInfo->readProcPtr = readproc;
-
- // Install the processing procedure (if any). Lack of a processing procedure knocks one level
- // of interrupt processing out, so this is a good way to save time and decrease the minimum
- // buffer size...
- dbInfo->processingProcPtr = processproc;
-
- dbInfo->refNum = fileRefNum; // store file ref num
- dbInfo->a5ref = SetCurrentA5 ();
-
- // We're essentially going to take over the specified sound channel to do double buffering with;
- // as a result, we'll install our own callback and put out private data structures in the userInfo
- // field. Just to be nice, we're even going to save the values that were there when we started,
- // in case we had something we didn't want stomped on in them...
- if (chan->userInfo) // Valid userInfo?
- dbInfo->oldUserInfo = chan->userInfo; // save it
- chan->userInfo = (long) dbInfo; // pointer to our vars...
-
- DebugMessage ("\pAbout to prime buffers");
-
- dbInfo->bytesToGo = playSize; // Set up play size.
- dbInfo->fileDataStart = dataOffset; // Offset into data stream
-
- err = PrimeBuffers (dbInfo, generalHeader); // fill both buffers...
- if (err != noErr) {
- // If we got to here, we got one [ED:censored] of an error trying to read the buffers,
- // so now we commit programatic Seppuku (hope this knife's sharp!!)
- Assert (err != noErr, "\pHit an error trying to Fill buffers");
- FreeDBPrivateMem (dbInfo); // Say bye2 to memory
- } else {
- DebugMessage ("\pSuccessfully primed buffers");
- // So, presumably at least one of our buffers has been filled, so let's set the chain reaction in
- // motion. Note that there is a possibility that we got only one buffer half full. No worries: the
- // callback routine will handle that nicely!!
-
- dbInfo->bCmd.cmd = bufferCmd;
- dbInfo->bCmd.param1 = nil;
- dbInfo->bCmd.param2 = (long) dbInfo->buffers[0].header;
-
- dbInfo->cbCmd.cmd = callBackCmd;
- dbInfo->cbCmd.param1 = 0;
- dbInfo->cbCmd.param2 = nil;
-
- err = QueueFrame (chan, dbInfo);
- DebugMessage ("\pJust finnished queueing up the first frame");
- }
- }
- }
- return (err);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // Calling this function kills a play operation in progress
- void KillDoubleBuffer (void)
- {
- gsStopFlag = true;
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // Queues up a bufferCmd followed by a callBackCmd.
- OSErr QueueFrame (SndChannelPtr chan, PrivateDBInfoPtr dbInfo)
- {
- OSErr err = noErr;
- register unsigned long *length = nil;
- register short *flags = nil;
-
- // "Why", you justifiably ask, "are you bothering to move these simple structure elemets into
- // temporary local variables?". Well, MPW C genereates awful code for each of these compares and
- // assignments. Since this routine is often executed at interrupt time, and is therefore quite
- // time-critical, I'm trying to squeze as much performance as possible out of our "Challenged"
- // compiler... Also, it makes things a tad more readable...
-
- length = &(dbInfo->buffers[dbInfo->currentBuffer].header->length);
- flags = &(dbInfo->buffers[dbInfo->currentBuffer].flags);
-
- if (*length && (*flags == kBufferReady)) { // if we have data & it's ready
- err = SndDoCommand (chan,&dbInfo->bCmd,true); // queue up current buffer
- Assert ((err != noErr), "\pCouldn't queue up the buffer ;g");
- if (err) // Got an error??
- return (err); // Yeah - Bail.
- else {
- *flags = kBufferPlaying; // Mark the buffer playing
- if (*length == kBufferSize) { // Unless this was the last
- dbInfo->cbCmd.param1 = dbInfo->currentBuffer; // buffer callback is for
- err = SndDoCommand (chan,&dbInfo->cbCmd,true); // queue up a callback
- Assert ((err != noErr), "\pCouldn't queue up the callback ;g");
- if (err) // Check for errors
- return (err); // got one? bail.
- }
- }
- } else {
- Assert ((*flags), "\pTried to queue a buffer that wasn't ready ;g");
- Assert ((*length == 0), "\pTried to queue a buffer that was empty ;g");
- return (channelBusy);
- }
-
- dbInfo->currentBuffer = NextHeader (dbInfo->currentBuffer); // go on to next buffer
-
- return (noErr);
- }
-
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // DBService is the callback procedure that takes care of swapping buffers and
- // refilling them.
- pascal void DBService (SndChannelPtr chan,SndCommand* acmd)
- {
- OSErr err = noErr;
- PrivateDBInfoPtr dbInfo = nil;
- short usedBuffer = 0;
- long oldA5 = nil;
-
- dbInfo = (PrivateDBInfoPtr) chan->userInfo; // get private info ptr
-
- usedBuffer = acmd->param1;
-
- oldA5 = SetA5 (dbInfo->a5ref);
-
- // If the play operation hasn't been canceled
- if (gsStopFlag != true) {
-
- // Use the bufferCmd from the next header
- dbInfo->bCmd.param2 = (long)dbInfo->buffers[dbInfo->currentBuffer].header;
- err = QueueFrame (chan, dbInfo); // play the frame
- Assert ((err), "\pcouldn't queue up buffer... ;g");
- if (err) {
- SetA5 (oldA5);
- return;
- }
-
- dbInfo->buffers[usedBuffer].flags = kBufferFilling;
-
- Assert ((dbInfo->buffers[usedBuffer].readPB.pb.ioResult == 1), "\pthe parameter block was busy ;g");
- if (dbInfo->buffers[usedBuffer].readPB.pb.ioResult == 1) {
- SetA5 (oldA5);
- return;
- }
-
- // Now, refill the exhausted buffer with some fresh data. Frankly, I haven't figured
- // out what to do about errors returned at this point. Suggestions welcome...
- err = (*(dbInfo->readProcPtr)) (dbInfo, usedBuffer, true);
- }
- SetA5 (oldA5);
- }
-
- //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- // completion routine for asynchronous reads
- void CompleteRead (void)
- {
- OSErr err = noErr;
- ExtParmBlkPtr callPB = nil;
- PrivateDBInfoPtr dbInfo = nil;
- long oldA5 = nil;
- register DeferredTask *dt = nil;
- register SampleBufferPtr buffer = nil;
-
- err = getErr(); // get the result
- callPB = (ExtParmBlkPtr) getPB(); // and our paramblock
- dbInfo = (PrivateDBInfoPtr) callPB->userInfo; // *and* our privates..
-
- // Again, we're trying to squeeze a little extra speed out of our friend, the MPW C compiler.
- // We make lots of similar references to "dbInfo->buffers[callPB->headerNum].dt", so lets
- // move it down to a register pointer variable...
- dt = &(dbInfo->buffers[callPB->headerNum].dt);
- buffer = &(dbInfo->buffers[callPB->headerNum]);
-
- oldA5 = SetA5 (dbInfo->a5ref);
-
- switch (err) {
- case eofErr :
- case noErr : // harmless error codes
- buffer->header->length = callPB->pb.ioActCount; // fill size in correctly
- if (dbInfo->processingProcPtr) {
- buffer->flags = kBufferProcessing; // mark buffer processing
- dt->qLink = 0;
- dt->qType = dtQType;
- dt->dtFlags = 0;
- dt->dtReserved = 0;
- dt->dtAddr = dbInfo->processingProcPtr;
- dt->dtParam = (long) &(dbInfo->buffers[callPB->headerNum]);
- QuickDTInstall (&buffer->dt);
- } else
- buffer->flags = kBufferReady; // mark buffer processing
- break;
- default: // some other error??
- dbInfo->bytesToGo = nil; // kill off buffering
- buffer->header->length = 0;
- }
- SetA5 (oldA5);
- }
-
-